package com.yang.strategy.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.yang.domain.dto.RedisData;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.yang.constants.RedisConstants.LOCK_CACHE_KEY;
/**
 * <p>
 * 缓存逻辑过期策略
 * </p>
 * @author yang
 * @date 2023/8/13
 */
@Service("queryWithLogicalExpire")
public class QueryWithLogicalExpire extends AbstractCacheStrategyImpl{

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;

    private static final ThreadPoolExecutor CACHE_REBUILD_EXECUTOR = new ThreadPoolExecutor(
            3,
            6,
            10,
            TimeUnit.MINUTES,
            new LinkedBlockingDeque<>(),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    @Override
    public <R, ID> R getCache(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        // 拼接key
        String key = keyPrefix + id;

        // 判断缓存是否过期
        String valueJson = stringRedisTemplate.opsForValue().get(key);

        if (StrUtil.isBlank(valueJson)){
            return null;
        }

        RedisData redisData = BeanUtil.toBean(valueJson, RedisData.class);
        R data = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();

        // 没过期，刷新有效期，返回
        if (expireTime.isAfter(LocalDateTime.now())){
            return data;
        }
        // 过期，开启独立线程，返回
        RLock lock = redissonClient.getLock(LOCK_CACHE_KEY + id);

        try {
            // 尝试获取锁
            boolean isLock = lock.tryLock(5, 5, TimeUnit.SECONDS);

            // 获取锁失败，返回过期数据
            if (!isLock) {
                return data;
            }

            // 异步缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                R newData = dbFallback.apply(id);
                //缓存重建
                super.setCache(key, newData, time, unit,true);
            });

            // 返回
            return data;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            lock.unlock();
        }
    }
}
